import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import {
  ActivityListDto,
  ActivityLoadDto,
  ActivityOrderDto,
  ActivityQueryDto,
  TokenLogQueryDto,
} from '@/dtos';
import {
  ActivityStatusEnum,
  ActivityTypeEnum,
  PointsLogTypeEnum,
  TokenLogTypeEnum,
  TokenSourceEnum,
  TokenStatusEnum,
} from '@/enums';
import { halt } from '@/utils';
import { Injectable, Logger } from '@nestjs/common';
import { Activity, Prisma, TokenLog } from '@prisma/client';
import dayjs from 'dayjs';

@Injectable()
export class ActivityService extends BaseService {
  async getList({ params, sort }: ActivityListDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    const whereType = where.type
      ? `AND t.type = ${where.type}`
      : `AND t.type <> ${ActivityTypeEnum.CAPSULE}`;

    let sql = `
      SELECT #{fields} FROM Activity AS t
      WHERE t.isDeleted = false
        AND t.isEnabled = true
        ${whereType}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Activity[]>(
      `${sql.replace(
        '#{fields}',
        `
        t.id, t.title, t.image, t.isSoldOut, t.sort, t.isEnabled, t.isUseWhiteList,
        t.isUsePoints, t.points, t.startTime, t.endTime, t.limit, t.perLimit, t.userLimit, t.type
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.sort, t.startTime DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((item) => ({
        ...item,
        points: Number(item.points),
      })),
    };
  }

  async getRecord({ params, sort }: TokenLogQueryDto, uid, type) {
    const { current = 1, pageSize = 20 } = params;

    let keyword = '合成';
    if (type == 2) {
      keyword = '扭蛋机';
    }

    let sql = `
      SELECT #{fields} FROM TokenLog AS log
      LEFT JOIN Token AS t ON t.id = log.tokenId
      LEFT JOIN Asset AS a ON a.id = t.assetId
      WHERE log.type not in (6, 7)
      AND log.isDeleted = false
      AND log.recipientId = ${uid}
      AND log.log LIKE '%${keyword}%'
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(log.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<TokenLog[]>(
      `${sql.replace(
        '#{fields}',
        `
        log.id, log.type, log.createdAt,
        t.no, t.assetId, a.title, a.image
      `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.id DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list,
    };
  }

  async getList2(userId: number) {
    const activity = await this.prisma.activity.findFirst({
      where: {
        type: ActivityTypeEnum.CAPSULE,
        isDeleted: false,
        isEnabled: true,
      },
      select: {
        id: true,
        title: true,
        image: true,
        isSoldOut: true,
        sort: true,
        isEnabled: true,
        burnPlans: true,
        isUseWhiteList: true,
        isUsePoints: true,
        points: true,
        startTime: true,
        endTime: true,
        limit: true,
        perLimit: true,
        userLimit: true,
        type: true,
      },
      orderBy: {
        id: 'desc',
      },
    });

    let chance = 0;
    if (activity) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-expect-error
      activity.points = Number(activity.points);

      const burnPlan = activity.burnPlans[0];
      if (!!burnPlan) {
        const asset = JSON.parse(burnPlan.plans[0].asset);
        // num: burnPlan.plans[0].num,
        // assetId: asset.id,
        // assetTitle: asset.title,

        if (!!burnPlan.plans[0].num) {
          const user = await this.prisma.user.findUnique({
            where: {
              id: userId,
              isBlockd: false,
              isDeleted: false,
            },
          });
          if (!user) {
            return halt('用户信息异常');
          }
          if (!user.isReal) {
            return halt('请先完成实名认证');
          }

          const holdCount = await this.prisma.token.count({
            where: {
              assetId: asset.id,
              status: TokenStatusEnum.HOLD,
              ownerId: user.id,
              isDeleted: false,
            },
          });

          chance = Math.trunc(holdCount / burnPlan.plans[0].num);
          console.log(`${holdCount} / ${burnPlan.plans[0].num}`);
        }
      }
    }

    return { activity, chance };
  }

  async query({ params, sort }: ActivityQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM Activity AS t
      WHERE t.isDeleted = false
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Activity[]>(
      `${sql.replace(
        '#{fields}',
        `
        t.id, t.title, t.image, t.isSoldOut, t.sort, t.isEnabled, t.isUseWhiteList, t.burnPlans, t.mintPlans,
        t.isUsePoints, t.points, t.startTime, t.endTime, t.tips, t.status, t.limit, t.perLimit, t.userLimit, 
        t.type, t.prompt, t.content
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.sort, t.startTime DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    // console.log(await this.redis.scard(`activity:80:stocks`));
    // await this.redis.scard(`activity:stocks:${item.id}`)
    const stocksWithId = [];
    for (const activity of list) {
      stocksWithId[activity.id] = await this.redis.scard(
        `activity:stocks:${activity.id}`,
      );
    }

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((item) => ({
        ...item,
        points: Number(item.points),
        stocks: stocksWithId[item.id] ?? 0,
      })),
    };
  }

  async findOne(
    where: Prisma.ActivityWhereUniqueInput,
    select?: Prisma.ActivitySelect,
  ): Promise<Activity | null> {
    return this.prisma.activity.findUnique({
      where,
      select,
    });
  }

  async create(data: Prisma.ActivityCreateInput): Promise<Activity> {
    const create = await this.prisma.activity.create({
      data: {
        ...data,
        ...(data.startTime
          ? { startTime: dayjs(data.startTime.toString()).toDate() }
          : {}),
        ...(data.endTime
          ? { endTime: dayjs(data.endTime.toString()).toDate() }
          : {}),
      },
    });

    if (create?.limit >= 1) {
      // 设置库存
      const assetIds = [];
      (create.mintPlans as any).map((item) => {
        item.plans.map((iItem) => {
          const asset = JSON.parse(iItem.asset);
          if (asset.id) {
            assetIds.push(asset.id);
          }
        });
      });

      if (assetIds.length > 0) {
        for (const assetId of assetIds) {
          const tokens = await this.prisma.token.findMany({
            where: {
              assetId: assetId,
              ownerId: constants.default.ownerId,
              isDeleted: false,
              status: TokenStatusEnum.HOLD,
            },
            select: {
              id: true,
            },
            orderBy: {
              hash: 'desc',
            },
          });

          const tokenIds = (tokens as any).map((item) => {
            return item.id;
          });

          if (tokenIds.length > 0) {
            await this.redis.sadd(
              `activity:asset_stock:${assetId}`,
              ...tokenIds,
            );
          }
        }
      }

      const pool = [];
      for (let i = 1; i <= create.limit; i++) {
        pool.push(i);
      }
      await this.redis.sadd(`activity:stocks:${create.id}`, ...pool);
    }

    return create;
  }

  async update(params: {
    where: Prisma.ActivityWhereUniqueInput;
    data: Prisma.ActivityUpdateInput;
  }): Promise<Activity> {
    const { where, data } = params;
    return this.prisma.activity.update({
      where,
      data: {
        ...data,
        ...(data.startTime
          ? { startTime: dayjs(data.startTime.toString()).toDate() }
          : {}),
        ...(data.endTime
          ? { endTime: dayjs(data.endTime.toString()).toDate() }
          : {}),
      },
    });
  }

  async remove(where: Prisma.ActivityWhereUniqueInput): Promise<Activity> {
    return this.prisma.activity.update({
      where,
      data: {
        isDeleted: true,
      },
    });
  }

  async order(
    { id, planId, num }: ActivityOrderDto,
    userId: number,
    ip: string,
    mobile?: string,
  ) {
    await this.util.wait(userId, ip);

    return await this.redlock.using([`user:${userId}`], 10000, {}, async () => {
      const { times, isWhiteList } = await this.getTimes(id, userId, mobile);

      if (num > times) {
        num = times;
      }

      if (num <= 0) {
        return halt('系统太火爆，请稍后重试');
      }

      if (num > 1) {
        return halt('系统太火爆，请稍后再试');
      }

      const user = await this.prisma.user.findUnique({
        where: {
          id: userId,
          isBlockd: false,
          isDeleted: false,
        },
      });
      if (!user) {
        return halt('用户信息异常');
      }
      if (!user.isReal) {
        return halt('请先完成实名认证');
      }
      const activity = await this.prisma.activity.findUnique({
        where: {
          id,
          isDeleted: false,
        },
      });
      if (!activity.isEnabled) {
        return halt('数据异常');
      }
      if (activity.isSoldOut) {
        return halt('活动已结束');
      }
      const currentTime = dayjs();
      if (currentTime.isBefore(dayjs(activity.startTime))) {
        return halt('活动尚未开始');
      }
      if (currentTime.isAfter(dayjs(activity.endTime))) {
        return halt('活动已经结束');
      }
      if (Number(num) > Number(activity.perLimit)) {
        return halt(`每次数量不能超过${activity.perLimit}`);
      }
      const stocks = await this.redis.scard(`activity:stocks:${activity.id}`);

      if (stocks <= 0) {
        // Logger.debug(`活动${activity.id}库存${stocks}`);
        return halt('库存耗尽');
      }

      if (Number(num) > Number(stocks)) {
        return halt(`合成数量超出库存数量`);
      }

      if (
        !times ||
        isNaN(times) ||
        !num ||
        isNaN(num) ||
        times <= 0 ||
        times < num
      ) {
        return halt(activity.tips ?? '可用资格不足');
      }

      const burnPlans = activity.burnPlans;
      const burnPlan = burnPlans[planId];
      let burnItems = [];
      if (!!burnPlan) {
        burnItems = burnPlan.plans.map((p) => {
          const asset = JSON.parse(p.asset);
          return {
            num: p.num,
            assetId: asset.id,
            assetTitle: asset.title,
          };
        });
        if (!!burnItems) {
          for (let o of burnItems) {
            const count = await this.prisma.token.count({
              where: {
                assetId: o.assetId,
                ownerId: user.id,
                isDeleted: false,
                status: TokenStatusEnum.HOLD,
              },
            });
            if (count < Number(o.num) * num) {
              return halt(`可消耗${o.assetTitle}不足`);
            }
          }
          Logger.debug('材料充足');
        }
      }

      // 总参与次数限制
      const stockNums: any = [];
      for (let i = 0; i < num; i++) {
        const num = await this.redis.spop(`activity:stocks:${activity.id}`);
        if (num) {
          stockNums.push(num);
        }
      }

      if (stockNums.length != num) {
        if (stockNums.length > 0) {
          await this.redis.sadd(`activity:stocks:${activity.id}`, ...stockNums);
        }
        return halt(`剩余库存数不足${num}`);
      }

      return await this.redlock.using(
        [`activity:${id}`],
        20000,
        {},
        async () => {
          return await this.prisma.$transaction(async (tx) => {
            console.time(`activity:${id}`);
            // let burnItems = [];
            // if (!!burnPlan) {
            //   burnItems = burnPlan.plans.map((p) => {
            //     const asset = JSON.parse(p.asset);
            //     return {
            //       num: p.num,
            //       assetId: asset.id,
            //       assetTitle: asset.title,
            //     };
            //   });
            //   if (!!burnItems) {
            //     for (let o of burnItems) {
            //       const count = await tx.token.count({
            //         where: {
            //           assetId: o.assetId,
            //           ownerId: user.id,
            //           isDeleted: false,
            //           status: TokenStatusEnum.HOLD,
            //         },
            //       });
            //       if (count < Number(o.num) * num) {
            //         return halt(`可消耗${o.assetTitle}不足`);
            //       }
            //     }
            //     console.log('材料充足');
            //   }
            // }

            const mintPlans = (activity.mintPlans as any).map((item) => {
              return {
                rate: item.rate,
                plans: item.plans.map((p) => {
                  const asset = JSON.parse(p.asset);
                  return {
                    num: p.num,
                    assetId: asset.id,
                    assetTitle: asset.title,
                    image: asset.image,
                  };
                }),
              };
            });

            const randomList: number[] = [];

            for (let i = 0; i < mintPlans.length; i++) {
              const p = mintPlans[i];
              if (p.rate <= 0) {
                continue;
              } else {
                for (let o of p.plans) {
                  const count = await tx.token.count({
                    where: {
                      assetId: o.assetId,
                      ownerId: constants.default.ownerId,
                      isDeleted: false,
                      status: TokenStatusEnum.HOLD,
                    },
                  });
                  // Logger.debug(`合成目标共${Number(o.num) * num}个`);
                  if (count < Number(o.num) * num) {
                    continue;
                  }
                  for (
                    let j = 0;
                    j < (p.rate < 1 ? p.rate * 10 : p.rate);
                    j++
                  ) {
                    randomList.push(i);
                  }
                }
              }
            }

            if (randomList.length <= 0) {
              return halt('库存不足');
            }

            const fromUser = await tx.user.findUnique({
              where: { id: constants.default.ownerId },
            });
            if (!fromUser) {
              return halt(`库存信息异常`);
            }

            // Logger.debug('库存充足', randomList);
            let messages = [];
            let image = '';

            for (let i = 0; i < num; i++) {
              let planIdx =
                randomList[Math.floor(Math.random() * randomList.length)];
              let plan = mintPlans[planIdx];
              // Logger.debug('选中方案:', plan);

              try {
                image = plan.plans[0].image;
              } catch (error) {
                // console.log(error);
              }

              // 铸造,todo 脏读产生的重复发货情况，会导致同一个token先后铸造给两个人，先铸造的人失去这个token
              for (let o of plan.plans) {
                for (let i = 0; i < Number(o.num); i++) {
                  // const token = await tx.token.findFirst({
                  //   where: {
                  //     assetId: o.assetId,
                  //     ownerId: constants.default.ownerId,
                  //     isDeleted: false,
                  //     status: TokenStatusEnum.HOLD,
                  //   },
                  //   orderBy: {
                  //     hash: 'desc',
                  //   },
                  // });
                  const token = await this.redis.spop(
                    `activity:asset_stock:${o.assetId}`,
                  );

                  if (token == null) {
                    // if (!token) {
                    return halt('销毁铸造异常');
                  }
                  await tx.token.update({
                    where: {
                      id: Number(token),
                      ownerId: constants.default.ownerId,
                    },
                    data: {
                      source: TokenSourceEnum.COMPOSE,
                      ownerId: user.id,
                    },
                  });

                  if (activity.type == ActivityTypeEnum.CAPSULE) {
                    await tx.tokenLog.create({
                      data: {
                        log: `扭蛋机:TOKEN_ID: ${token}`,
                        tokenId: Number(token),
                        type: TokenLogTypeEnum.TRANSFER,
                        fromId: constants.default.ownerId,
                        recipientId: user.id,
                      },
                    });
                  } else {
                    await tx.tokenLog.create({
                      data: {
                        log: `合成:TOKEN_ID: ${token}`,
                        tokenId: Number(token),
                        type: TokenLogTypeEnum.TRANSFER,
                        fromId: constants.default.ownerId,
                        recipientId: user.id,
                      },
                    });
                  }
                }
                await tx.$executeRawUnsafe(
                  `
                    UPDATE \`Asset\` 
                    SET circulation = circulation + ${Number(o.num)}, updatedAt = NOW() 
                    WHERE id = ${o.assetId}
                  `,
                );

                messages.push(`${o.assetTitle} * ${Number(o.num)}`);
              }

              // 销毁
              if (!!burnItems) {
                Logger.debug('销毁', burnItems);
                for (let o of burnItems) {
                  for (let i = 0; i < Number(o.num); i++) {
                    const token = await tx.token.findFirst({
                      where: {
                        assetId: o.assetId,
                        ownerId: user.id,
                        isDeleted: false,
                        status: TokenStatusEnum.HOLD,
                      },
                      orderBy: {
                        hash: 'desc',
                      },
                    });
                    if (!token) {
                      return halt('销毁资产异常');
                    }
                    await tx.token.update({
                      where: {
                        id: token.id,
                      },
                      data: {
                        status: TokenStatusEnum.DESTROYED,
                        isDeleted: true,
                      },
                    });
                    await tx.tokenLog.create({
                      data: {
                        log: `销毁: ${token.no}`,
                        tokenId: token.id,
                        type: TokenLogTypeEnum.BURN,
                        fromId: user.id,
                      },
                    });
                  }
                  await tx.$executeRawUnsafe(
                    `
                    UPDATE \`Asset\` 
                    SET circulation = circulation - ${Number(o.num)}, updatedAt = NOW() 
                    WHERE id = ${o.assetId}
                  `,
                  );
                }
              }
            }

            for (let i = 0; i < num; i++) {
              const time = dayjs().format('YYYY-MM-DD HH:mm:ss');
              await this.redis.lpush(
                `activity:user:${activity.id}:${user.mobile}`,
                time,
              );
              await this.redis.lpush(`activity:total:${activity.id}`, time);
            }

            console.timeEnd(`activity:${id}`);

            if (activity.type == ActivityTypeEnum.CAPSULE) {
              return {
                message: messages.join(','),
                image: image,
              };
            } else {
              return messages.join(',');
            }
          });
        },
      );
    });
  }

  async load({ id, rows }: ActivityLoadDto) {
    const activity = await this.prisma.activity.findUnique({
      where: {
        id,
      },
    });
    if (!activity) {
      return halt('目标不存在');
    }

    await this.util.cleanKeys(`activity:total:${id}:*`);
    await this.util.cleanKeys(`activity:user:${id}:*`);
    await this.util.cleanKeys(`activity:whitelist:${id}:*`);

    if (activity.isUseWhiteList) {
      for (let item of rows) {
        if (!!item.mobile) {
          await this.redis.set(
            `activity:whitelist:${activity.id}:${item.mobile}`,
            Number(item.num),
          );
        }
      }
    }

    await this.prisma.activity.update({
      where: {
        id,
      },
      data: {
        status: ActivityStatusEnum.COMPLETED,
      },
    });

    return true;
  }

  async getTimes(
    id: number,
    userId: number,
    mobile?: string,
  ): Promise<{ times: number; isWhiteList?: boolean }> {
    // const user = await this.prisma.user.findUnique({
    //   where: {
    //     id: userId,
    //     isBlockd: false,
    //     isDeleted: false,
    //   },
    // });
    // if (!user) {
    //   return halt('用户信息异常');
    // }
    // if (!user.isReal) {
    //   return 0;
    // }
    const activity = await this.prisma.activity.findUnique({
      where: { id },
    });
    if (!activity) {
      return halt('数据异常');
    }
    if (!activity.isEnabled) {
      return { times: 0 };
    }
    if (activity.isSoldOut) {
      return { times: 0 };
    }
    const limit = await this.redis.llen(`activity:user:${id}:${mobile}`);
    if (activity.isUseWhiteList) {
      const whitelist = await this.redis.get(
        `activity:whitelist:${id}:${mobile}`,
      );
      if (!whitelist || Number(whitelist) <= 0) {
        return { times: 0 };
      }
      return { times: Number(whitelist) - limit, isWhiteList: true };
    }
    return { times: activity.userLimit - limit };
  }

  async getTotal(id: number) {
    const activity = await this.prisma.activity.findUnique({
      where: { id },
    });
    if (!activity) {
      return halt('数据异常');
    }
    const total = await this.redis.llen(`activity:total:${activity.id}`);
    return activity.totalLimit - Number(total);
  }
}
